package org.dei.perla.rockfall.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.dei.perla.sys.device.fpc.dspic.datastructures.DSPicOnEventStatus;
import org.dei.perla.sys.device.fpc.dspic.datastructures.DSPicOnEventData;
import org.dei.perla.sys.device.fpc.dspic.datastructures.ED;
import org.dei.perla.sys.device.fpc.dspic.datastructures.LowSamplingRateData;
import org.dei.perla.sys.device.fpc.dspic.datastructures.MRP;
import org.dei.perla.utils.dataconverter.ArrayUtility;
import org.dei.perla.utils.dataconverter.DataConverter;
import org.dei.perla.utils.messages.AdapterFpcMessage;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.pipe.PipePair;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

public class DSPicDataGenerator {
	
	private static int registeredObjects = 0;
	
	private Object synchObject;
	
	private ArrayList<Pipe<AdapterFpcMessage>> outPipes;
	private ArrayList<Pipe<AdapterFpcMessage>> inPipes;
	
	private Waiter<Pipe<AdapterFpcMessage>> inPipesWaiter;
	private Thread inPipesWaiterThread; 
	
	private MRP mrp;
	
	public DSPicDataGenerator() {
		synchObject = new Object();
		inPipesWaiter = new Waiter<Pipe<AdapterFpcMessage>>();
		inPipesWaiter.start();
		inPipes = new ArrayList<Pipe<AdapterFpcMessage>>();
		outPipes = new ArrayList<Pipe<AdapterFpcMessage>>();
		
		mrp = new MRP();
		mrp.setAcquisitionType(0);
		mrp.setCommand(5);
		mrp.setGx(4);
		mrp.setGy(5);
		mrp.setGz(6);
		mrp.setRs1(10);
		mrp.setRs2(11);
		mrp.setRs3(12);
		mrp.setThdwX(8);
		mrp.setThdwY(9);
		mrp.setThdwZ(10);
		mrp.setThupX(50);
		mrp.setThupY(51);
		mrp.setThupZ(52);
		int[] taps = new int[64];
		for (int i = 0; i < taps.length; i++) {
			taps[i] = i;
		}
		mrp.setTaps(taps);
		
		inPipesWaiterThread = new Thread(new UpdateAttributesReaderThread());
		inPipesWaiterThread.start();
	}
	
	/**
	 * Termina il generatore di classi ed invia un pacchetto killer verso tutti i
	 * componenti collegati tramite pipe
	 */
	public void stop() {
		synchronized (synchObject) {
			for (Pipe<?> p : outPipes) {
				p.enqueue(null);
			}
			for (Pipe<?> p : inPipes) {
				p.enqueue(null);
			}
		}
	}
	
	/**
	 * Crea nuovi messaggi e li invia sulle pipe
	 */
	public void putDataOnPipe() {
		synchronized (synchObject) {
			for (Pipe<AdapterFpcMessage> p : outPipes) {
				p.enqueue(createDataFile());
			}
		}
	}
	
	private AdapterFpcMessage createDataFile() {
		AdapterFpcMessage adapterFpcMessage = new AdapterFpcMessage();
		
		DSPicOnEventStatus status = new DSPicOnEventStatus();
		status.setData(generateDSPicStatusOnEvent());
		status.setLowSamplingRateData(generateLowSamplingRateData());
		status.setActualParams(mrp);
		
		adapterFpcMessage.setPayload(ArrayUtility.toPrimitiveByteArray(DataConverter.toByteArray(status)));
		return adapterFpcMessage;
	}
	
	private LowSamplingRateData generateLowSamplingRateData() {
		LowSamplingRateData lowSamplingRateData = new LowSamplingRateData();
		lowSamplingRateData.setAttitudeX(getRandomShort());
		lowSamplingRateData.setAttitudeY(getRandomShort());
		lowSamplingRateData.setAttitudeZ(getRandomShort());
		lowSamplingRateData.setCrackMeter1(getRandomShort());
		lowSamplingRateData.setCrackMeter2(getRandomShort());
		lowSamplingRateData.setTemperature(getRandomShort());
		
		return lowSamplingRateData;
	}
	
	private DSPicOnEventData generateDSPicStatusOnEvent() {
		DSPicOnEventData statusOnEvent = new DSPicOnEventData();
		int eventDataCount = Double.valueOf(Math.random()*22).byteValue();
		statusOnEvent.setEventDataCount(eventDataCount);
		ArrayList<Byte> eventSamples = new ArrayList<Byte>();
		ED[] eventData = new ED[22]; 
		for (int i = 0; i < eventDataCount; i++) {
			eventSamples.addAll(generateSingleEventSamples());
			eventData[i] = generateEventData(i);
		}
		for (int i = eventDataCount; i < 22; i ++) {
			eventData[i] = generateEmptyEventData();
		}
		Byte[] eventSamplesByte = new Byte[eventSamples.size()];
		eventSamplesByte = eventSamples.toArray(eventSamplesByte);
		statusOnEvent.setEventDataSamples(ArrayUtility.toPrimitiveByteArray(eventSamplesByte));
		statusOnEvent.setEventData(eventData);
		statusOnEvent.setNumEvents(getRandomUnsignedByte() + eventDataCount);
		
		return statusOnEvent;
	}
	
	private ED generateEventData(int i) {
		ED eventData = new ED();
		eventData.setFlags(getRandomUnsignedByte());
		eventData.setMagnitudeX(getRandomUnsignedShort());
		eventData.setMagnitudeY(getRandomUnsignedShort());
		eventData.setMagnitudeZ(getRandomUnsignedShort());
		eventData.setNoiseX(getRandomUnsignedShort());
		eventData.setNoiseY(getRandomUnsignedShort());
		eventData.setNoiseZ(getRandomUnsignedShort());
		eventData.setPeakX(getRandomByte());
		eventData.setPeakY(getRandomByte());
		eventData.setPeakZ(getRandomByte());
		eventData.setSamples(54);
		eventData.setStart(i * 54);
		eventData.setStop(eventData.getStart() + 53);
		eventData.setTimestamp(System.currentTimeMillis() + i);
		
		return eventData;
	}
	
	private ED generateEmptyEventData() {
		ED eventData = new ED();
		eventData.setFlags(0);
		eventData.setMagnitudeX(0);
		eventData.setMagnitudeY(0);
		eventData.setMagnitudeZ(0);
		eventData.setNoiseX(0);
		eventData.setNoiseY(0);
		eventData.setNoiseZ(0);
		eventData.setPeakX(0);
		eventData.setPeakY(0);
		eventData.setPeakZ(0);
		eventData.setSamples(0);
		eventData.setStart(0);
		eventData.setStop(0);
		eventData.setTimestamp(0);
		
		return eventData;
	}
	
	private List<Byte> generateSingleEventSamples() {
		Byte[] eventSamples = new Byte[54];	
		for (int i = 0; i < eventSamples.length; i++) {
			eventSamples[i] = new Byte(getRandomByte());
		}
		
		return Arrays.asList(eventSamples);
	}
	
	private byte getRandomByte() {
		if (Math.random() > 0.5) {
			return Double.valueOf(Math.random()*Byte.MAX_VALUE).byteValue();
		} else {
			return Double.valueOf(Math.random()*Byte.MIN_VALUE).byteValue();
		}
	}
	
	private byte getRandomUnsignedByte() {
		return Double.valueOf(Math.random()*Byte.MAX_VALUE).byteValue();	
	}
	
	private short getRandomUnsignedShort() {
		return Double.valueOf(Math.random()*Short.MAX_VALUE).shortValue();
	}
	
	private short getRandomShort() {
		if (Math.random() > 0.5) {
			return Double.valueOf(Math.random()*Short.MAX_VALUE).shortValue();
		} else {
			return Double.valueOf(Math.random()*Short.MIN_VALUE).shortValue();
		}
	}
	
	/**
	 * Crea ed avvia una coppia di pipes che altri componenti possono utilizzare per
	 * comunicare con il DataFilesGenerator
	 * @return oggetto PipePair contenente le pipes da utilizzare
	 */
	public PipePair<AdapterFpcMessage, AdapterFpcMessage> getCommunicationPipes() {
		Pipe<AdapterFpcMessage> pipeIn = new Pipe<AdapterFpcMessage>("Log Files Generator - pipeIn_" + registeredObjects);
		Pipe<AdapterFpcMessage> pipeOut = new Pipe<AdapterFpcMessage>("Log Files Generator - pipeOut_" + registeredObjects++);
		pipeIn.start();
		pipeOut.start();
		inPipesWaiter.addWaitable(pipeIn.getWaitable());
		synchronized (synchObject) {
			inPipes.add(pipeIn);
			outPipes.add(pipeOut);
		}
		return new PipePair<AdapterFpcMessage, AdapterFpcMessage>(pipeOut, pipeIn);
	}
	
	private class UpdateAttributesReaderThread implements Runnable {

		public void run() {
			Waitable<Pipe<AdapterFpcMessage>> waitable;
			Pipe<AdapterFpcMessage> pipe;
			
			while (true) {
				waitable = inPipesWaiter.waitNext();
				pipe = waitable.getParentComponent();
				// Scarto i pacchetti di update, e nel caso siano null li interpreto come
				// terminazione dell'FPC
				if (pipe.dequeue() == null) {
					pipe.stop();
					inPipesWaiter.removeWaitable(waitable);
					synchronized (synchObject) {
						inPipes.remove(pipe);
					}
					if (inPipes.size() == 0) {
						break;
					}
				}
			}
		}
	}
}
